Plugin API
November 19, 2025Plugin API
Введение
Plugin API позволяет разработчикам создавать собственные плагины (драйверы) для интеграции новых устройств с системой BARY. Плагины работают как отдельные процессы и взаимодействуют с основным приложением через IPC (Inter-Process Communication).
Возможности:
- Поддержка любых IoT устройств и протоколов
- Изоляция драйверов в отдельных процессах
- Автоматическая установка зависимостей
- Логирование и мониторинг
- Публикация событий в реальном времени
- Mixins — модульное расширение функциональности
- Templates — конфигурационные файлы устройств
- Динамические capabilities — генерация интерфейса в runtime
- Settings_ex — динамические настройки с фильтрацией
Установка и структура проекта
Готовый шаблон
Для быстрого старта используйте готовый пустой шаблон плагина:
GitHub репозиторий: https://github.com/gapaus/bary_plugin_empty
Этот шаблон содержит всю необходимую структуру, конфигурационные файлы, базовые классы и инструменты для разработки плагинов BARY.
Базовая структура плагина
my-plugin/
├── package.json # Зависимости и метаданные
├── tsconfig.json # Конфигурация TypeScript
├── webpack.config.js # Сборка плагина
├── debug.sh # Скрипт отладки
├── nodemon.json # Конфигурация dev-режима
├── src/
│ ├── mixins/ # Пользовательские миксины (опционально)
│ │ ├── params.ts
│ │ └── ipc.ts
│ ├── my-plugin.ts # Основной файл плагина
│ └── my-plugin.json # Метаданные плагина для BARY
├── core/ # Базовые классы (наследуются)
│ ├── base-module.ts
│ └── base-driver-module.ts
├── enums/
│ └── EventTypes.ts # Типы событий
├── lib/ # Вспомогательные библиотеки
│ ├── better-queue/ # Система очередей
│ ├── foibles/ # Система миксинов
│ ├── require-ex.ts # Автоматическая установка зависимостей
│ └── shared.functions.ts
├── templates/ # Конфигурационные файлы устройств (опционально)
│ └── device-config.json
└── dist/ # Скомпилированные файлы
package.json
{
"name": "bary-plugin-mydevice",
"version": "1.0.0",
"main": "src/my-plugin.ts",
"scripts": {
"build": "webpack --display_modules",
"debug": "nodemon --config nodemon.json"
},
"dependencies": {
"async-mutex": "^0.5.0",
"moment": "^2.27.0",
"node-ipc": "^9.1.1",
"winston": "^3.17.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "^14.14.34",
"ts-loader": "^8.0.14",
"ts-node": "^10.3.0",
"typescript": "^4.1.3",
"webpack": "^4.46.0",
"webpack-cli": "^3.2.3"
}
}
my-plugin.json
Метаданные и конфигурация плагина для BARY:
{
"name": "My Device Plugin",
"type": 1,
"icon": "hub",
"class_name": "my-plugin",
"module": "my-plugin",
"manufacturer": "Company Name",
"cloud": false,
"support_autoupdate": false,
"dynamic_capabilities": true,
"mac_address": false,
"capabilities": [],
"sort_index": 4,
"sub_devices": [
{
"class_name": "my-plugin.subdevice",
"name": "My SubDevice",
"dynamic_capabilities": true,
"mac_address": false,
"support_autoupdate": true,
"autoupdate_interval": 500,
"selectable": true,
"connect_config": true,
"type": 39,
"sort_index": 1,
"settings": [
{
"key": "identifier",
"name": "Device ID",
"type": "text",
"required": true
}
],
"commands": [
{
"command": "reset"
}
]
}
],
"settings": [
{
"key": "port",
"name": "Port",
"type": "text",
"required": true,
"defaultValue": "/dev/ttyUSB0"
},
{
"key": "baud_rate",
"name": "Baud rate",
"type": "select",
"items": [
{"id": 9600, "title": "9600"},
{"id": 19200, "title": "19200"},
{"id": 115200, "title": "115200"}
],
"defaultValue": 9600
},
{
"key": "enable_logging",
"name": "Enable logging",
"type": "checkbox"
}
],
"commands": [
{
"command": "scan_devices"
},
{
"command": "reset"
}
],
"display": [
{
"functions": [
{
"title": "Scan Mode",
"type": "scan_mode",
"view": "buttons",
"model": "device.:ident.status.scan_mode",
"buttons": [
{"value": "auto", "title": "Automatic"},
{"value": "manual", "title": "Manual"}
],
"data": {
"ident": ":ident",
"command": "scan_mode",
"value": ":scan_mode"
}
}
]
}
],
"dependencies": {
"my-device-sdk": "^2.0.0"
}
}
Основные поля:
name— отображаемое имя плагинаtype— уникальный тип устройства (число)icon— иконка плагинаclass_name— техническое имя классаmodule— имя модуляmanufacturer— производитель (опционально)cloud— поддержка облакаsupport_autoupdate— автообновление статусаdynamic_capabilities— capabilities генерируются в runtimemac_address— требуется MAC-адресdisable_display_sort— отключить сортировку отображенияcapabilities— статические возможности устройстваsub_devices— конфигурация дочерних устройствsettings— статические настройки плагинаcommands— команды плагинаdisplay— кастомизация UI (опционально)dependencies— NPM-зависимости (устанавливаются автоматически)
Базовые классы
baseModule
Базовый класс для всех плагинов, обеспечивает IPC коммуникацию с BARY.
Основные свойства:
config— конфигурация приложенияipc— IPC клиент для связи с BARYevents— массив зарегистрированных событийrequestId— счетчик запросовexternal_driver— флаг внешнего драйвера
Основные методы:
// Отправка запроса с ожиданием ответа
request(eventName: string, params: object): Promise<any>
// Отправка запроса без ожидания ответа
requestEx(eventName: string, params: object): void
// Логирование
log(message: any): void
error(message: any): void
// Динамическая загрузка npm модулей
require(ident: string, require: boolean): Promise<any>
baseDriverModule
Расширяет baseModule дополнительными методами для работы с драйверами устройств.
Дополнительные свойства:
device_id— ID устройстваparams— параметры устройстваconfig— конфигурация устройстваlogger— Winston logger для записи логовident— идентификатор устройстваappDevices— список устройств приложенияstatusCache— кэш статусов для оптимизацииpluginTemplate— метаданные плагина из JSON
Жизненный цикл:
// 1. Инициализация плагина
initDeviceEx(resolve, reject): void
// 2. Подключение к устройству
connectEx(resolve, reject): void
// 3. Обработка команд
commandEx(command, value, params, options, resolve, reject, status): void
// 4. Получение списка дочерних устройств
getSubDevicesEx(resolve, reject, zones): void
// 5. Обработка событий от подписанных устройств (опционально)
onSubscribeDevice(params: any): void
Дополнительные методы:
// Создание/обновление дочернего устройства
checkSubDevice(model, key, name, params, zone_id): Promise<any>
// Публикация событий
publish(eventType, ...params): void
publishStatus(eventType, status): void
// Формирование имени события для статуса
eventTypeStatus(className, identifier?, key?): string
// Подписка на события другого устройства
subscribeDevice(ident, eventType): void
// Уведомления
sendNotify(message, options?): void
sendNotifyEx(body): void
sendPushNotification(message, email, title): void
// Работа с устройствами
getDevices(params?): Promise<Device[]>
deviceCommand(ident, command, data, value): Promise<any>
deviceEvent(ident, event, data): Promise<any>
// Работа с БД
getTable(table, options): Promise<any[]>
createTable(table, options): Promise<any>
updateTable(table, options, where): Promise<any>
getConfig(): Promise<any>
getDeviceCustomData(ident, custom_data_id): Promise<any>
// Загрузка templates
loadTemplate(ident, name, options?): any
// Очереди
startQueue(ident): void
countQueue(ident): void
doneQueue(ident, resolve, reject, inc?, error?): void
Расширение функциональности через Mixins
Что такое Mixins?
BARY использует библиотеку foibles для создания миксинов — модульного способа расширения функциональности классов.
Создание миксина
// src/mixins/params.ts
const {Mixin} = require('../../lib/foibles');
export const DEVICE_ID_INC = 10000;
export const Params = Mixin(parent => class Params extends parent {
capabilities = [];
settings_ex = [];
get heater_list() {
return this.params?.heater_list || [];
}
get heater_list_devices() {
return this.heater_list
? this.appDevices.filter(item =>
this.heater_list.find(item1 => item1 === item.ident)
)
: [];
}
getDeviceParam(params, device, key, defaultValue) {
return params[`${device.ident}_${key}`] !== undefined
? params[`${device.ident}_${key}`]
: defaultValue;
}
updateCapabilities() {
this.capabilities = [];
this.heater_list_devices.forEach(device => {
this.capabilities.push({
ident: 'power',
index: DEVICE_ID_INC + device.id,
display_name: `${device.name} (${device.zone_name})`,
options: {
info: `info_${DEVICE_ID_INC + device.id}`,
ignore_changes: true
}
});
});
}
});
Использование миксинов
import {baseDriverModule} from '../core/base-driver-module';
import {Params, DEVICE_ID_INC} from './mixins/params';
import {IPC} from './mixins/ipc';
// Расширение класса несколькими миксинами
class MyPlugin extends baseDriverModule.with(Params, IPC) {
connectEx(resolve, reject) {
this.getDevices({currentStatus: true}).then(devices => {
this.appDevices = devices;
// Использование методов из миксина Params
this.updateCapabilities();
// Доступ к геттерам миксина
this.heater_list_devices.forEach(device => {
console.log(device.name);
});
// Публикация capabilities
this.publish(
this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
{capabilities: this.capabilities, settings_ex: this.settings_ex}
);
resolve({});
});
}
}
const app = new MyPlugin();
Templates — конфигурационные файлы устройств
Создание templates
Плагины могут хранить конфигурационные файлы в папке /templates/:
my-plugin/
└── templates/
├── device1.json
├── device2.json
└── conditioner.json
Пример template
{
"manufacturer": "Philio Technology Corp",
"manufacturerId": "0x013c",
"label": "PAR01",
"description": "Conditioner",
"devices": [
{
"productType": "0x0106",
"productId": "0x8290"
}
],
"firmwareVersion": {
"min": "0.0",
"max": "255.255"
},
"paramInformation": [
{
"#": "25",
"label": "Learn mode",
"valueSize": 2,
"minValue": 0,
"maxValue": 2048,
"defaultValue": 1
},
{
"#": "27",
"label": "Temperature",
"valueSize": 2,
"minValue": 16,
"maxValue": 30,
"defaultValue": 22
}
]
}
Загрузка template
// В методе плагина
const template = this.loadTemplate('my-plugin', 'conditioner.json');
console.log(template.manufacturer); // "Philio Technology Corp"
// С опциями (если template — это функция)
const template = this.loadTemplate('my-plugin', 'device-template', {
deviceId: 123
});
Создание плагина
Минимальный пример
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
class MyPlugin extends baseDriverModule {
/**
* Инициализация плагина
*/
initDeviceEx(resolve, reject) {
super.initDeviceEx(() => {
this.app.log('Плагин инициализирован');
// Создание имени события для статуса
this.eventName = this.eventTypeStatus(
this.pluginTemplate.class_name,
this.id
);
resolve({});
}, reject);
}
/**
* Подключение к устройству
*/
connectEx(resolve, reject) {
this.app.log('Подключение к устройству...');
// Получение списка устройств
this.getDevices({currentStatus: true}).then(devices => {
this.appDevices = devices;
// Подписка на события устройств
this.appDevices.forEach(device => {
this.subscribeDevice(device.ident, 'changed_power');
});
// Публикация динамических capabilities
const capabilities = [];
capabilities.push({
ident: 'power',
index: 1,
display_name: 'Включить/выключить',
options: {
hideTitle: false
}
});
this.publish(this.eventName, {capabilities});
});
resolve({});
}
/**
* Обработка команд
*/
commandEx(command, value, params, options, resolve, reject, status) {
this.app.log('Команда:', command, 'Значение:', value);
// Проверка прав администратора (если нужно)
if (command === 'admin_action' && options && !options.is_admin) {
return reject({message: 'Access Denied!'});
}
switch(command) {
case 'on':
// Включить устройство
resolve({state: 'on'});
break;
case 'off':
// Выключить устройство
resolve({state: 'off'});
break;
case 'status':
// Получить статус
resolve({connected: true, state: 'on'});
break;
default:
reject({message: 'Неизвестная команда'});
}
}
/**
* Обработка событий от подписанных устройств
*/
onSubscribeDevice(params: any) {
const device = this.appDevices.find(item => item.ident === params.ident);
if (device) {
device.currentStatus = params.currentStatus;
// Реакция на изменение статуса
this.handleDeviceUpdate(device);
}
}
}
// Создание экземпляра плагина
const app = new MyPlugin();
app.logging = true;
Динамические Capabilities
Capabilities можно генерировать динамически в методе connectEx() или при изменении конфигурации.
Публикация capabilities
connectEx(resolve, reject) {
const capabilities = [];
// Простой переключатель
capabilities.push({
ident: 'power',
index: 1,
display_name: 'Управление отоплением',
options: {
hideTitle: false,
hideSeparator: false,
hideBottomPadding: false,
ignore_changes: false,
read_only: false
}
});
// Текстовое поле с измерением
capabilities.push({
ident: 'text',
index: 2,
display_name: 'Температура',
options: {
measure: '°C',
read_only: true
}
});
// Ползунок (range)
capabilities.push({
ident: 'target_temperature',
index: 3,
display_name: 'Желаемая температура',
options: {
minValue: 5,
maxValue: 30,
stepValue: 0.5,
hideTitle: true
}
});
// Capability с информационным полем
capabilities.push({
ident: 'power',
index: 10,
display_name: 'Насос 1',
options: {
info: 'info_10', // Связано с status.info_10
ignore_changes: true
}
});
// Заголовок
capabilities.push({
ident: 'title',
index: 100,
options: {
title_only: 'Климат-контроль'
},
hide_background: true,
hide_bottom_padding: true,
hide_separator: true
});
// Capability для добавления/удаления устройств
capabilities.push({
ident: 'power',
index: 1,
display_name: 'Add Z-Wave device',
options: {
link_devices: true
}
});
capabilities.push({
ident: 'power',
index: 2,
display_name: 'Remove Z-Wave device',
options: {
unlink_devices: true
}
});
// Публикация
this.publish(
this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
{capabilities}
);
resolve({});
}
Доступные опции capabilities
hideTitle— скрыть заголовокhideSeparator— скрыть разделительhideBottomPadding— скрыть отступ снизуignore_changes— игнорировать измененияread_only— только для чтенияinfo— связь с информационным полем (например,info_10связано сstatus.info_10)measure— единица измерения (например,°C,%,W)minValue,maxValue,stepValue— для ползунков (range)value_on,value_off— значения для переключателейtitle_only— только заголовок без фона (для ident: ‘title’)link_devices,unlink_devices— для добавления/удаления устройств
Динамические Settings (settings_ex)
Settings_ex позволяют создавать настройки с фильтрацией устройств по командам и типам.
Публикация settings_ex
connectEx(resolve, reject) {
const settings_ex = [];
// Выбор устройства с фильтрацией по командам
settings_ex.push({
key: 'temperature_sensor',
name: 'Датчик температуры',
type: 'select',
items: [],
driver_support: [ // Фильтр по поддерживаемым командам
'supportTemperature',
'supportTemperatureEx'
],
multi: false // Одиночный выбор
});
// Множественный выбор устройств
settings_ex.push({
key: 'heater_list',
name: 'Список обогревателей',
type: 'select',
items: [],
driver_support: ['supportPower', 'supportPowerEx'],
driver_types: [5], // Фильтр по типу драйвера
multi: true // Множественный выбор
});
// Выбор комнаты
settings_ex.push({
key: 'zone_id',
name: 'Комната',
type: 'select',
items: [],
zone_support: ['rooms'] // Показывать только комнаты
});
// Условная видимость
settings_ex.push({
key: 'advanced_options',
name: 'Дополнительные опции',
type: 'text',
visibleField: 'enable_advanced', // Показывать только если
visibleFieldValue: true // enable_advanced = true
});
// Текстовое поле с значением по умолчанию
settings_ex.push({
key: 'interval',
name: 'Интервал обновления (мин)',
type: 'text',
defaultValue: 5
});
// Заголовок (группировка настроек)
settings_ex.push({
key: 'group_heating',
name: 'Настройки отопления',
type: 'title'
});
// Динамическое создание настроек для каждого устройства
this.heater_list_devices.forEach(device => {
settings_ex.push({
key: `${device.ident}_kp`,
name: `${device.name} - Пропорциональная составляющая`,
type: 'text',
defaultValue: 0.5
});
});
// Публикация вместе с capabilities
this.publish(
this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
{capabilities, settings_ex}
);
resolve({});
}
Доступные опции settings_ex
key— ключ настройкиname— отображаемое имяtype— тип поля (text,select,checkbox,title)items— элементы дляselectdefaultValue— значение по умолчаниюrequired— обязательное полеdriver_support— фильтрация устройств по поддерживаемым командамdriver_types— фильтрация устройств по типуmulti— множественный выбор (дляselect)zone_support— фильтрация зон (['rooms']— только комнаты)visibleField— поле, от которого зависит видимостьvisibleFieldValue— значение поля для отображенияdescription— описание настройкиgroup— группа настроек (например,settingsDevice)
API методы
Работа с устройствами
Создание дочернего устройства
checkSubDevice(
model: string, // Модель устройства (например, 'zigbee2mqtt.light')
key: string, // Уникальный ключ
name: string, // Название устройства
params: object, // Параметры устройства
zone_id: number // ID зоны (null для автоматического определения)
): Promise<any>
Параметры устройства:
{
icon: 'light', // Иконка устройства
identifier: 'unique_id', // Уникальный идентификатор
capabilities: [ // Возможности устройства
{
index: 0,
property: 'state',
ident: 'state',
display_name: 'Состояние',
access: 'rw', // r=read, w=write, rw=read+write
homekit: true, // Поддержка HomeKit
yandex: true, // Поддержка Яндекс Алиса
sber: true, // Поддержка Сбер
options: {
value_on: true,
value_off: false,
minValue: 0,
maxValue: 100,
stepValue: 1
}
}
],
settings_ex: [ // Динамические настройки устройства
{
key: 'parameter_1',
name: 'Parameter 1',
type: 'text',
defaultValue: 10
}
],
parent_identifier: 123 // ID родительского устройства
}
Доступные иконки:
- Освещение:
light,chandelier,rgb_lamp,rgb_strip,rgb_led,table_lamp,spotlight,sconce,facade_light - Развлечения:
tv,gamepad,music_note,speaker - Безопасность:
camera,security,intercom,doorlock,night,smoke,gas_leak,leak - Дом:
home,gates,gate1,gate2,gate3,gate4,socket,infrared,faucet,valve,pump,hub - Шторы/жалюзи:
view_column,louvers,marquise - Климат:
floor_heater,heater,heater1,convector,humidifier,ac_unit,fan,dryer,cooling,anti-icing - Датчики:
co2,voc,p2,multisensor,temp_sensor,humidity_sensor,door_sensor,counter,motion - Бытовая техника:
kettle,exhaust_hood,fridge,washing_machine,microwave,dish_washer,oven,stove,vacuum_cleaner,pool,squirt
Типы плагинов (driver_types):
BARY поддерживает 52 типа плагинов для различных устройств и сервисов:
- Шлюзы и сервисы: Шлюзы (1), Облачные сервисы (2), Провайдеры услуг (19), Система (39), Внешние API для камеры (43), Музыкальные сервисы (45)
- Медиа устройства: Игровые приставки (7), Ресиверы (8), Телевизоры (9), ТВ-приставки (18), Умные колонки (37), Интерактивные панели (50)
- Умный дом базовый: Умные розетки (3), Выключатели (12), Релейные модули (14), Эмуляторы IR/RF (16), Умные лампочки (22), Освещение (25), RGB Контроллеры (40), Диммеры (44)
- Климат-контроль: Термостаты (13), Кондиционеры (17), Системы вентиляции (33), Умные вентиляторы (41), Климат-контроль (47), Увлажнители (6)
- Датчики: Датчики температуры (11), Датчики влажности (23), Датчики освещенности (24), Датчики протечки (26), Датчики движения (27), Датчики напряжения (28), Датчики УФ (29), Датчики открытия окон и дверей (30), Комбинированные датчики (31), Детекторы дыма (36), Мониторы качества воздуха (21)
- Безопасность: Камеры (5), Дверные замки (32), Домофоны (42), Распознавание ГРЗ (48), СКУД (49)
- Бытовая техника: Пылесосы (4), Приводы штор (38), Уход за растениями (20)
- Инженерные системы: Счетчики электроэнергии (15), Водосчетчики (35), Умные контроллеры (34), Модули ввода-вывода (46), Модули управления устройствами (51)
- Голосовое управление: Голосовые ассистенты (52), Погода (10)
Получение списка устройств
getDevices(params?: object): Promise<Device[]>
Пример:
const devices = await this.getDevices({currentStatus: true});
devices.forEach(device => {
this.app.log('Устройство:', device.name, 'Статус:', device.currentStatus);
});
Подписка на события устройства
subscribeDevice(ident: string, eventType: string): void
Пример:
// Подписка на изменение питания
this.subscribeDevice('device-001', 'changed_power');
// Обработка в onSubscribeDevice()
onSubscribeDevice(params: any) {
const device = this.appDevices.find(item => item.ident === params.ident);
if (device) {
device.currentStatus = params.currentStatus;
this.app.log('Статус изменился:', device.name, params.currentStatus);
}
}
Отправка команды другому устройству
deviceCommand(
ident: string, // Идентификатор устройства
command: string, // Команда
data: object, // Данные
value: any // Значение
): Promise<any>
Пример:
await this.deviceCommand('light_bedroom', 'on', {}, true);
Публикация событий
Формирование имени события
eventTypeStatus(
className: string,
identifier?: string,
key?: string
): string
Пример:
const eventName = this.eventTypeStatus('my-plugin', 'device-001', 'temperature');
// Результат: "status->my-plugin->device-001->temperature"
// Для плагина
const eventName = this.eventTypeStatus(this.pluginTemplate.class_name, this.id);
// Результат: "status->my-plugin->driver-123"
Публикация статуса устройства
publishStatus(eventType: EventTypes, status: object): void
Пример:
// Отправка данных с датчика
this.publishStatus(EventTypes.UpdateTemperature, {
temperature_living_room: 22.5,
humidity_living_room: 45,
connected: true
});
Оптимизация публикации:
Метод publishStatus автоматически:
- Публикует только измененные данные
- Отправляет числовые данные при изменении >10% или раз в 15 секунд
- Всегда отправляет события датчиков движения, открытия и т.д.
Прямая публикация событий
publish(eventType: EventTypes | string, ...params: any[]): void
Пример:
this.publish(EventTypes.ChangedMotion, {
parent_identifier: this.device_id,
motion_sensor_1: true
});
// Публикация capabilities и settings_ex
this.publish(
this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
{capabilities, settings_ex, displays: [...]}
);
Уведомления
Отправка уведомления пользователю
sendNotify(message: string, options?: object): void
Пример:
this.sendNotify('Устройство подключено успешно!');
Системные уведомления (sendNotifyEx)
sendNotifyEx(body: object): void
Пример:
// Обновление устройства
this.sendNotifyEx({
system: true,
type: 'device-update',
ident: this.ident
});
// Обновление настроек
this.sendNotifyEx({
system: true,
type: 'settings-update',
ident: this.ident
});
Push-уведомление
sendPushNotification(
message: string,
email: string,
title: string
): void
Пример:
this.sendPushNotification(
'Обнаружено движение в гостиной',
'user@example.com',
'Датчик движения'
);
Работа с данными
Получение конфигурации
getConfig(): Promise<any>
Работа с базой данных
// Получение записей
getTable(table: string, options: object): Promise<any[]>
// Создание записи
createTable(table: string, options: object): Promise<any>
// Обновление записей
updateTable(table: string, options: object, where: object): Promise<any>
Пример:
// Получение всех устройств типа 'light'
const lights = await this.getTable('devices', {
where: {type: 'light'}
});
// Создание записи
await this.createTable('custom_data', {
device_id: 123,
key: 'last_seen',
value: new Date().toISOString()
});
// Обновление
await this.updateTable(
'devices',
{name: 'Updated Name'},
{where: {id: 123}}
);
Облачные запросы
cloudRequest(params: object): Promise<any>
Работа с очередями (Better-queue)
// Создание очереди запросов
const Queue = require('../lib/better-queue/queue');
this.requestQueue = new Queue((task, callback) => {
this.deviceCommand(
task.ident,
task.command,
{},
task.value
).then(() => {
callback(null, {});
}).catch(error => {
callback(error);
});
}, {
concurrent: 1, // Последовательная обработка
maxRetries: 3
});
// Добавление задачи в очередь
this.requestQueue.push({
ident: 'device-001',
command: 'set_power',
value: true
}, (error, result) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Success:', result);
}
});
// Встроенные методы для простых очередей
this.startQueue('commands');
this.countQueue('commands');
this.doneQueue('commands', resolve, reject);
Типы событий (EventTypes)
Устройства
DeviceCreate— создание устройстваDeviceUpdate— обновление устройстваDeviceDelete— удаление устройстваDeviceConnect— подключение устройстваDeviceDiscover— обнаружение нового устройства
Обновления данных
UpdateTemperature— обновление температурыUpdateThermostatTemp— обновление температуры термостатаUpdatePower— обновление мощностиUpdateAlert— предупреждение
События датчиков
ChangedMotion— изменение датчика движенияChangedMagnet— изменение геркона (открытие/закрытие)ChangedMagnetChange— изменение геркона с передачей измененийChangedPower— изменение состояния питанияChangedPowerChange— изменение состояния питания с передачей измененийChangedPowerLoad— изменение нагрузкиChangedAction— пользовательское действиеChangedLeakChange— обнаружение протечкиChangedInputChange— изменение входаChangedCounterChange— изменение счетчикаChangedChannel— изменение каналаChangedChannel0— изменение канала 0ChangedChannel1— изменение канала 1
MQTT
MqttMessage— сообщение MQTT
Статистика
NewStats— новая статистикаNewStatsEx— расширенная статистика
Wi-Fi
WiFiConnect— подключение Wi-FiWiFiConnected— Wi-Fi подключенWiFiNetworkFound— сеть Wi-Fi найдена
Приложение
ApplicationReady— приложение готовоApplicationCreated— приложение созданоApplicationGetDevices— получение устройств приложенияApplicationDeviceCommand— команда устройствуApplicationAddDeviceQueue— добавление устройства в очередьApplicationAddScanQueue— добавление сканирования в очередьApplicationDriverReady— драйвер готовNewEvent— новое событие
База данных
DatabaseReady— БД готоваDatabaseConnected— БД подключенаDatabaseGetAllItems— получение всех элементовDatabaseUpdateItem— обновление элементаDatabaseUpdateDeviceParams— обновление параметров устройстваDatabaseCreateItem— создание элементаDatabaseQuery— запрос к БДDatabaseUpdate— обновление БДDatabaseQueueQuery— запрос в очереди БД
Прочее
CheckSubDevice— проверка дочернего устройстваPublish— публикацияNotify— уведомлениеUpdateSensor— обновление датчикаRemoveSensor— удаление датчикаUpdateSensorEx— расширенное обновление датчика
Примеры плагинов
Пример 1: Плагин для HTTP устройств
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
import axios from 'axios';
class HttpDevicePlugin extends baseDriverModule {
private baseUrl: string;
initDeviceEx(resolve, reject) {
super.initDeviceEx(() => {
// Получаем URL из параметров
this.baseUrl = this.params.url || 'http://192.168.1.100';
this.app.log('HTTP устройство:', this.baseUrl);
resolve({});
}, reject);
}
connectEx(resolve, reject) {
// Проверяем доступность устройства
axios.get(`${this.baseUrl}/status`)
.then(response => {
this.app.log('Устройство онлайн');
this.startPolling();
resolve({});
})
.catch(error => {
reject({message: 'Устройство недоступно'});
});
}
startPolling() {
setInterval(() => {
axios.get(`${this.baseUrl}/sensors`)
.then(response => {
this.publishStatus(EventTypes.UpdateTemperature, {
temperature_sensor: response.data.temperature,
humidity_sensor: response.data.humidity,
connected: true
});
})
.catch(error => {
this.app.error('Ошибка получения данных:', error.message);
});
}, 10000); // Каждые 10 секунд
}
commandEx(command, value, params, options, resolve, reject, status) {
switch(command) {
case 'on':
axios.post(`${this.baseUrl}/control`, {power: 'on'})
.then(() => resolve({state: 'on'}))
.catch(error => reject({message: error.message}));
break;
case 'off':
axios.post(`${this.baseUrl}/control`, {power: 'off'})
.then(() => resolve({state: 'off'}))
.catch(error => reject({message: error.message}));
break;
case 'status':
axios.get(`${this.baseUrl}/status`)
.then(response => resolve(response.data))
.catch(error => reject({message: error.message}));
break;
default:
reject({message: 'Неизвестная команда'});
}
}
}
const app = new HttpDevicePlugin();
app.logging = true;
Пример 2: Плагин с динамическими capabilities и миксинами
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
import {Params} from './mixins/params';
class DashboardPlugin extends baseDriverModule.with(Params) {
appDevices = [];
initDeviceEx(resolve, reject) {
super.initDeviceEx(() => {
this.app.log('Dashboard plugin initialized');
this.eventName = this.eventTypeStatus(
this.pluginTemplate.class_name,
this.id
);
resolve({});
}, reject);
}
connectEx(resolve, reject) {
this.getDevices({currentStatus: true}).then(devices => {
this.appDevices = devices;
// Подписка на события устройств
this.appDevices.forEach(device => {
this.subscribeDevice(device.ident, 'changed_power');
});
// Динамическое создание capabilities
this.updateCapabilities();
this.updateSettingsEx();
// Публикация capabilities и settings_ex
this.publish(this.eventName, {
capabilities: this.capabilities,
settings_ex: this.settings_ex
});
resolve({});
});
}
updateCapabilities() {
this.capabilities = [];
const deviceCount = this.params.device_count || 0;
// Создание capabilities для каждого устройства
for (let i = 0; i < deviceCount; i++) {
const deviceIdent = this.params[`device_ident_${i}`];
const deviceType = this.params[`device_type_${i}`];
if (deviceIdent) {
this.capabilities.push({
ident: 'power',
index: i + 1,
display_name: `Device ${i + 1}`,
options: {
hideTitle: false,
info: `info_${i}`,
ignore_changes: false
}
});
}
}
}
updateSettingsEx() {
this.settings_ex = [];
const deviceCount = this.params.device_count || 4;
// Заголовок
this.settings_ex.push({
key: 'devices_group',
name: 'Devices Configuration',
type: 'title'
});
// Создание настроек для каждого устройства
for (let i = 0; i < deviceCount; i++) {
// Тип устройства
this.settings_ex.push({
key: `device_type_${i}`,
name: `Device ${i + 1} Type`,
type: 'select',
items: [
{id: 'switch', title: 'Switch'},
{id: 'sensor', title: 'Sensor'},
{id: 'light', title: 'Light'}
],
defaultValue: 'switch'
});
// Выбор устройства с фильтрацией
this.settings_ex.push({
key: `device_ident_${i}`,
name: `Device ${i + 1}`,
type: 'select',
items: [],
driver_support: ['supportPower'],
multi: false
});
}
}
onSubscribeDevice(params: any) {
// Обработка событий от подписанных устройств
const device = this.appDevices.find(item => item.ident === params.ident);
if (device) {
device.currentStatus = params.currentStatus;
this.app.log('Device status updated:', device.name, params.currentStatus);
// Публикация обновленного статуса
this.publishStatus(EventTypes.UpdatePower, {
[`status_${device.ident}`]: params.currentStatus.power
});
}
}
commandEx(command, value, params, options, resolve, reject, status) {
// Проверка прав администратора
if (command === 'reset' && options && !options.is_admin) {
return reject({message: 'Access Denied!'});
}
switch(command) {
case 'update_config':
// Обновление конфигурации
this.updateCapabilities();
this.updateSettingsEx();
this.publish(this.eventName, {
capabilities: this.capabilities,
settings_ex: this.settings_ex
});
// Системное уведомление об обновлении настроек
this.sendNotifyEx({
system: true,
type: 'settings-update',
ident: this.ident
});
resolve({success: true});
break;
case 'status':
resolve({
connected: true,
devices_count: this.capabilities.length
});
break;
default:
reject({message: 'Неизвестная команда'});
}
}
}
const app = new DashboardPlugin();
app.logging = true;
Пример 3: Плагин-шлюз с дочерними устройствами
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
class GatewayPlugin extends baseDriverModule {
private devices: Map<string, any> = new Map();
initDeviceEx(resolve, reject) {
super.initDeviceEx(() => {
this.app.log('Шлюз инициализирован');
resolve({});
}, reject);
}
connectEx(resolve, reject) {
// Сканирование устройств
this.scanDevices().then(() => {
resolve({});
});
}
async scanDevices() {
// Обнаружение устройств (например, через Zigbee, Z-Wave и т.д.)
const foundDevices = [
{id: '0x123', type: 'light', name: 'Свет в гостиной'},
{id: '0x456', type: 'sensor', name: 'Датчик температуры'}
];
for (const device of foundDevices) {
await this.registerDevice(device);
}
}
async registerDevice(device: any) {
let model, icon, capabilities;
if (device.type === 'light') {
model = 'gateway.light';
icon = 'light';
capabilities = [
{
property: 'state',
ident: 'state',
display_name: 'Включено',
access: 'rw',
homekit: true,
yandex: true,
options: {value_on: true, value_off: false}
},
{
property: 'brightness',
ident: 'brightness',
display_name: 'Яркость',
access: 'rw',
homekit: true,
options: {minValue: 0, maxValue: 100, stepValue: 1}
}
];
} else if (device.type === 'sensor') {
model = 'gateway.temperature';
icon = 'temp_sensor';
capabilities = [
{
property: 'temperature',
ident: 'temperature',
display_name: 'Температура',
access: 'r',
scale: '°C'
}
];
}
const result = await this.checkSubDevice(
model,
device.id,
device.name,
{icon, identifier: device.id, capabilities},
null
);
this.devices.set(device.id, device);
this.app.log('Зарегистрировано устройство:', device.name);
}
commandEx(command, value, params, options, resolve, reject, status) {
const deviceId = options.ident;
// Отправка команды конкретному дочернему устройству
if (this.devices.has(deviceId)) {
// Здесь отправка команды через протокол шлюза
this.app.log('Команда для устройства', deviceId, ':', command, value);
resolve({success: true});
} else {
reject({message: 'Устройство не найдено'});
}
}
getSubDevicesEx(resolve, reject, zones) {
// Возврат списка всех дочерних устройств
const devices = Array.from(this.devices.values()).map(device => ({
class_name: 'GatewayDevice',
identifier: device.id,
name: device.name,
params: {
icon: device.type === 'light' ? 'light' : 'temp_sensor',
identifier: device.id
}
}));
resolve(devices);
}
}
const app = new GatewayPlugin();
app.logging = true;
Отладка плагинов
Локальный запуск
Для ручного тестирования плагина:
const app = new MyPlugin();
app.logging = true;
// Инициализация с тестовыми параметрами
app.initDevice({
params: {
url: 'http://192.168.1.100',
port: 80
}
}).then(() => {
// Подключение
app.connect({id: 1}).then(() => {
// Тестовая команда
app.command({
command: 'status',
id: 1
}).then(result => {
console.log('Результат:', result);
});
});
});
Логирование
// Разные уровни логирования
this.app.log('Информация'); // INFO
this.app.error('Ошибка'); // ERROR
this.app.debug('Отладка'); // DEBUG
this.app.info('Информация'); // INFO
// Логирование с автоматическим таймстампом и использованием памяти
// Формат: HH:mm:ss [memory] сообщение
Логи сохраняются в ${params.log_path}/${module}-${id}.log с ротацией:
- Максимальный размер файла: 10 МБ
- Количество файлов: 5
Параметры запуска
# Запуск с логированием
node dist/my-plugin.js 123 logging=true
# Удаленное подключение к BARY
node dist/my-plugin.js 123 host=192.168.1.100 port=8000
Лучшие практики
1. Обработка ошибок
commandEx(command, value, params, options, resolve, reject, status) {
try {
// Ваш код
resolve(result);
} catch (error) {
this.app.error('Ошибка выполнения команды:', error.message);
reject({message: error.message, ignore: false});
}
}
2. Переподключение при ошибках
connectEx(resolve, reject) {
const tryConnect = (attempts = 0) => {
this.connectToDevice()
.then(() => resolve({}))
.catch(error => {
if (attempts < 3) {
this.app.log(`Попытка переподключения ${attempts + 1}/3`);
setTimeout(() => tryConnect(attempts + 1), 5000);
} else {
reject({message: 'Не удалось подключиться'});
}
});
};
tryConnect();
}
3. Оптимизация публикации данных
// Используйте publishStatus для автоматической оптимизации
this.publishStatus(EventTypes.UpdateTemperature, {
temperature: 22.5, // Отправится только при изменении >10%
humidity: 45 // или раз в 15 секунд
});
// Для важных событий используйте publish напрямую
this.publish(EventTypes.ChangedMotion, {
motion: true // Отправится сразу
});
4. Использование миксинов для модульности
// Разделяйте функциональность на миксины
// mixins/params.ts — работа с параметрами
// mixins/ipc.ts — дополнительная IPC логика
// mixins/servo.ts — управление сервоприводами
class MyPlugin extends baseDriverModule.with(Params, IPC, Servo) {
// Чистый и модульный код
}
5. Управление зависимостями
В my-plugin.json указывайте только специфичные для вашего плагина зависимости:
{
"dependencies": {
"my-device-sdk": "^2.0.0",
"zwave-js": "^9.2.2"
}
}
BARY автоматически установит их при первом запуске плагина через RequireEx.
6. Использование очередей для последовательных операций
// Создайте очередь для команд устройству
const Queue = require('../lib/better-queue/queue');
this.requestQueue = new Queue((task, callback) => {
this.deviceCommand(task.ident, task.command, {}, task.value)
.then(() => callback(null, {}))
.catch(error => callback(error));
}, {
concurrent: 1,
maxRetries: 3
});
// Используйте очередь вместо прямых вызовов
this.requestQueue.push({
ident: device.ident,
command: 'set_power',
value: true
});
Сборка и развертывание
Компиляция
# Установка зависимостей
npm install
# Сборка
npm run build
# Отладка с автоперезагрузкой
npm run debug
Поддержка и документация
- Официальный сайт: https://bary.io
- Email: support@bary.io
- Telegram: https://t.me/bary_io